#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <stdarg.h>
#include <resolv.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define MAXBUF 1024 
#define PORT 55555
#define CLIENT 0
#define SERVER 1

int debug;
char *progname; 

//显示证书信息
void ShowCerts(SSL * ssl) 
{
    X509 * cert;
    char * line;
    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) 
    {
        printf("数字证书信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("证书: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("颁发者: %s\n", line);
        free(line);
        X509_free(cert);
    } 
    else 
    {
        printf("无证书信息！\n");
    }
} 

 //创建tun设备
int tun_alloc(char *dev, int flags) {

  struct ifreq ifr;
  int fd, err;
  char *clonedev = "/dev/net/tun";

  if( (fd = open(clonedev , O_RDWR)) < 0 ) {
    perror("Opening /dev/net/tun");
    return fd;
  }

  memset(&ifr, 0, sizeof(ifr));

  ifr.ifr_flags = flags;

  if (*dev) {
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
  }

  if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {
    perror("ioctl(TUNSETIFF)");
    close(fd);
    return err;
  }

  strcpy(dev, ifr.ifr_name);

  return fd;
}

void do_debug(char *msg, ...)
{
	va_list argp;
	
	if(debug)
	{
		va_start(argp, msg);
		vfprintf(stderr, msg, argp);
		va_end(argp);
	}
}

void my_err(char *msg, ...)
{
	va_list argp;
  
	va_start(argp, msg);
	vfprintf(stderr, msg, argp);
	va_end(argp);
}

void usage(void) {
  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "%s -i <ifacename> [-s|-c <serverIP>] [-p <port>] [-u|-a] [-d]\n", progname);
  fprintf(stderr, "%s -h\n", progname);
  fprintf(stderr, "\n");
  fprintf(stderr, "-i <ifacename>: Name of interface to use (mandatory)\n");
  fprintf(stderr, "-s|-c <serverIP>: run in server mode (-s), or specify server address (-c <serverIP>) (mandatory)\n");
  fprintf(stderr, "-p <port>: port to listen on (if run in server mode) or to connect to (in client mode), default 55555\n");
  fprintf(stderr, "-u|-a: use TUN (-u, default) or TAP (-a)\n");
  fprintf(stderr, "-d: outputs debug information while running\n");
  fprintf(stderr, "-h: prints this help text\n");
  exit(1);
}

int main(int argc, char *argv[] )
{
	int sockfd, tun_fd, net_fd;
	int nread, nwrite, plength, option;
	int flags = IFF_TUN;
	char if_name[IFNAMSIZ];
	char buffer[MAXBUF + 1];
	char remote_ip[16] = "";
	unsigned short int port = PORT;
	int cliserv = -1;
	unsigned long int tun2net = 0, net2tun = 0;
	SSL_CTX * ctx;
	SSL * ssl;
	
	progname = argv[0];
	
	while((option = getopt(argc, argv, "i:sc:p:uahd")) > 0)
	{
		switch(option)
		{
		  case 'd':
			debug = 1;
			break;
		  case 'h':
			usage();
			break;
		  case 'i':
			strncpy(if_name, optarg, IFNAMSIZ-1);
			break;
		  case 's':
			cliserv = SERVER;
			break;
		  case 'c':
			cliserv = CLIENT;
			strncpy(remote_ip, optarg, 15);
			break;
		  case 'p':
		    port = atoi(optarg);
			break;
		  case 'u':
		    flags = IFF_TUN;
			break;
		  case 'a':
		    flags = IFF_TAP;
			break;
		  default:
		    my_err("Unknow option %c\n", option);
			usage();
		}
	}
	
	argv += optind;
	argc -= optind;
	
	if(argc > 0)
	{
		my_err("Too many options !\n");
		usage();
	}
	
	if(*if_name == '\0')
	{
		 my_err("Must specify interface name!\n");
		usage();
	}
	else if(cliserv < 0)
	{
		my_err("Must specify client or server mode!\n");
		usage();
	}
	else if((cliserv == CLIENT)&&(*remote_ip == '\0'))
	{
		my_err("Must specify server address!\n");
		usage();
	}
	
	if((tun_fd = tun_alloc(if_name, flags | IFF_NO_PI)) < 0)
	{
		printf("tun_alloc error\n");
		exit(1);
	}
	
	do_debug("Successfully connected to interface %s\n", if_name);
	
	if(cliserv == CLIENT)             //客户端
	{
		struct sockaddr_in dest;
		const SSL_METHOD *method;

		method = NTLS_client_method();
		ctx = SSL_CTX_new(method);
		if (ctx == NULL) 
		{
			ERR_print_errors_fp(stdout);
			exit(1);
		}

		SSL_CTX_enable_ntls(ctx);
		SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

		if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
		{
			perror("Socket");
			exit(errno);
		}

		bzero( &dest, sizeof(dest));
		dest.sin_family = AF_INET;
		dest.sin_port = htons(port);
		dest.sin_addr.s_addr = inet_addr(remote_ip);
		
		if (connect(sockfd, (struct sockaddr * ) &dest, sizeof(dest)) != 0) 
		{
			perror("Connect ");
			exit(errno);
		}

		ssl = SSL_new(ctx);
		SSL_set_fd(ssl, sockfd);
		if (SSL_connect(ssl) == -1) 
		{
			ERR_print_errors_fp(stderr);
		}
		else 
		{
			printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
			ShowCerts(ssl);
		}
		
		net_fd = sockfd;

	}
	else if(cliserv == SERVER)        //服务器端
	{
		socklen_t client_len;
		struct sockaddr_in my_addr, their_addr;
		unsigned int myport, lisnum = 5;
		const SSL_METHOD *method;

		method = NTLS_server_method();
		ctx = SSL_CTX_new(method);
		if (ctx == NULL) 
		{
			ERR_print_errors_fp(stdout);
			exit(1);
		}

		SSL_CTX_enable_ntls(ctx);

		/* Set the key and cert */
		if (!SSL_CTX_use_sign_certificate_file(ctx, "certs/sm2_sign.crt",
											   SSL_FILETYPE_PEM) ||
			!SSL_CTX_use_sign_PrivateKey_file(ctx, "certs/sm2_sign.key",
											  SSL_FILETYPE_PEM) ||
			!SSL_CTX_use_enc_certificate_file(ctx, "certs/sm2_enc.crt",
											  SSL_FILETYPE_PEM) ||
			!SSL_CTX_use_enc_PrivateKey_file(ctx, "certs/sm2_enc.key",
											 SSL_FILETYPE_PEM))
		{
			ERR_print_errors_fp(stderr);
			exit(1);
		}

		/* 开启一个socket 监听*/
		if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
		{
			perror("socket");
			exit(1);
		} 
		else 
		{
			printf("socket created\n");
		}
		
		bzero( &my_addr, sizeof(my_addr));
		my_addr.sin_family = AF_INET;
		my_addr.sin_port = htons(port);
		my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
		if (bind(sockfd, (struct sockaddr * ) &my_addr, sizeof(struct sockaddr)) == -1) 
		{
			perror("bind");
			exit(1);
		} 
		else
		{
			printf("binded\n");
		} 
		if (listen(sockfd, lisnum) == -1) 
		{
			perror("listen");
			exit(1);
		} 
		else 
		{
			printf("begin listen\n");// DEBUG
		}
		
			
		client_len = sizeof(struct sockaddr);
		/* 等待客户端连上来*/
		if ((net_fd = accept(sockfd, (struct sockaddr * ) & their_addr, &client_len)) == -1) 
		{
			perror("accept");
			exit(errno);
		} 
		else 
		{
			printf("server: got connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), net_fd);
		}
		/* 基于ctx 产生一个新的SSL */
		ssl = SSL_new(ctx);
		/* 将连接用户的socket 加入到SSL */
		SSL_set_fd(ssl, net_fd);
		/* 建立SSL 连接*/
		if (SSL_accept(ssl) == -1) 			
		{
			perror("accept");
			close(net_fd);		
		}
	}
	int maxfd = (tun_fd > net_fd)?tun_fd:net_fd;
	
	while(1)
	{
		int ret;
		fd_set rd_set;
		FD_ZERO(&rd_set);
		FD_SET(tun_fd, &rd_set);
		FD_SET(net_fd, &rd_set);
		
		ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL );
		
		if (ret < 0 && errno == EINTR)
		{
			continue;
		}

		if (ret < 0) 
		{
			perror("select()");
			exit(1);
		}
		
		if(FD_ISSET(tun_fd, &rd_set))
		{
			nread = read(tun_fd, buffer, MAXBUF);
			
			tun2net++;
			do_debug("TUN2NET %lu: Read %d bytes from the tap interface\n", tun2net, nread);

			//将数据包大小和数据包发出去
			plength = htons(nread);
			nwrite = SSL_write(ssl, (char *)&plength, sizeof(plength));
			nwrite = SSL_write(ssl, buffer, nread);
			do_debug("TUN2NET %lu: Written %d bytes to the network\n", tun2net, nwrite);
		}
		
		if(FD_ISSET(net_fd, &rd_set))
		{
			nread = SSL_read(ssl, (char *)&plength, sizeof(plength));
			if(nread == 0)
			{
				break;
			}
			
			net2tun++;
			
			nread = SSL_read(ssl, buffer, ntohs(plength));
			do_debug("NET2TUN %lu: Read %d bytes from the network\n", net2tun, nread);
			nwrite = write(tun_fd, buffer, nread);
			do_debug("NET2TUN %lu: Written %d bytes to the tap interface\n", net2tun, nwrite);
		}
	}
	
		server_finish:
		/* 关闭SSL 连接*/
		SSL_shutdown(ssl);
		/* 释放SSL */
		SSL_free(ssl);
		/* 关闭socket */
		close(net_fd);
			
		/* 关闭监听的socket */
		close(sockfd);
		/* 释放CTX */
		SSL_CTX_free(ctx);
		
	return 0;
}











